package xdb.util;

import com.goldhuman.Common.Marshal.MarshalException;
import com.goldhuman.Common.Marshal.OctetsStream;
import mysql.MySQLConf;
import mysql.Mysql4Merge;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 一个无日志的数据库。
 *
 * <p>可以用来处理数据导入导出，数据转换，等需要批量修改。
 * <p>线程安全，提供两种锁：DB.lock(), Table.lock()。
 */
public class Dbx {
    private static final Manager manager = new Manager();
    ////////////////////////////////////////////////////////////
    // Dbx 实现
    private final Lock dbLock = new ReentrantLock(); // 保护 DB 的所有操作。
    private final Mysql4Merge mySqlMgr;
    private final DatabaseMetaData metaData;
    private final Map<String, Table> tables = new HashMap<String, Table>();
    private final String dbName;
    private boolean closed = false;
    private ArrayList<Integer> zoneIds;
    /**
     * 建立数据库实例。
     *
     * @param dbConf   database connection properties
     * @param metaData database meta data.
     */
    private Dbx(MySQLConf dbConf, DatabaseMetaData metaData) {
        if (xdb.Xdb.isOpen()) {
            throw new IllegalAccessError("i hate xdb.");
        }
        this.mySqlMgr = new Mysql4Merge(dbConf);
        this.dbName = dbConf.getDbName();
        this.metaData = metaData;
    }

    public static Manager getManager() {
        return manager;
    }

    public static boolean start() {
        return manager.start(true);
    }

    public static void stop() {
        manager.stop();
    }

    public static Dbx open(MySQLConf dbConf, DatabaseMetaData metaData) {
        if (!manager.isOpen()) {
            throw new IllegalStateException("Dbx is stopped");
        }
        return manager.open(dbConf, metaData);
    }

    /**
     * 关闭数据库，
     */
    public void destroy() {
        dbLock.lock();
        try {
            if (false == closed) {
                Dbx.getManager().removeDbx(this);
                closed = true;
            }
        } finally {
            dbLock.unlock();
        }
    }

    /**
     * 关闭数据库。保存修改。
     */
    public void close() {
        dbLock.lock();
        try {
            if (false == closed) {
                //////////////////////////////////////////////////////////////////////////////
                Dbx.getManager().removeDbx(this);
                closed = true; // must after openTable
                assert (this.tables.isEmpty());
            }
        } finally {
            dbLock.unlock();
        }
    }

    /**
     * 获取对应连接的db
     *
     * @return
     */
    public String getDbName() {
        return dbName;
    }

    /**
     * 是否已经关闭。
     *
     * @return
     */
    public boolean isClosed() {
        dbLock.lock();
        try {
            return closed;
        } finally {
            dbLock.unlock();
        }
    }

    public DatabaseMetaData getMetaData() {
        return metaData;
    }

    /**
     * 返回所有打开的表。
     *
     * @return
     */
    public Table[] getTables() {
        dbLock.lock();
        try {
            return tables.values().toArray(new Table[tables.size()]);
        } finally {
            dbLock.unlock();
        }
    }

    public Table getTable(String tableName) {
        dbLock.lock();
        try {
            return tables.get(tableName);
        } finally {
            dbLock.unlock();
        }
    }

    /**
     * 打开表格。如果表格已经打开，则返回它。
     */
    public Table openTable(String tableName) {
        if (null == this.metaData.getTable(tableName)) {
            throw new IllegalStateException("table not found in MetaData: " + tableName);
        }
        return _openTable(tableName);
    }

    /**
     * 打开表格。如果表格已经打开，则返回它。
     */
    private Table _openTable(String tableName) {
        dbLock.lock();
        try {
            if (closed) {
                throw new IllegalStateException("Database has closed.");
            }
            Table table = tables.get(tableName);
            if (null == table) {
                table = new Table(tableName);
                tables.put(tableName, table);
            }
            return table;
        } finally {
            dbLock.unlock();
        }
    }

/*	private final Table removeTable(String tableName) {
		dbLock.lock();
		try {
			return this.tables.remove(tableName);
		} finally {
			dbLock.unlock();
		}
	}*/

    public final void lock() {
        dbLock.lock();
    }

    public final void unlock() {
        dbLock.unlock();
    }

    public Mysql4Merge getMySqlMgr() {
        return mySqlMgr;
    }

    public ArrayList<Integer> getZoneId() throws Exception {
        return this.zoneIds;
    }

    public void initZoneId(ArrayList<Integer> zoneIds) throws Exception {
        this.zoneIds = zoneIds;
    }

    public final static class Manager {
        private final Lock lock = new ReentrantLock();
        private final Map<String, Dbx> dbxs = new HashMap<String, Dbx>();
        private boolean opened = false;
        private volatile boolean saveAtExit = true;

        public Manager() {
            Runtime.getRuntime().addShutdownHook(new Thread("xdb.util.Dbx.ShutdownHook") {
                @Override
                public void run() {
                    Manager.this.stop();
                }
            });
        }

        public boolean isOpen() {
            lock.lock();
            try {
                return opened;
            } finally {
                lock.unlock();
            }
        }

        /**
         * @param saveAtExit save at exit.
         * @return
         */
        public boolean start(boolean saveAtExit) {
            lock.lock();
            try {
                if (opened) {
                    return false;
                }
                xdb.Trace.info("xdb.util.Dbx start ...");
                this.saveAtExit = saveAtExit;
                opened = true;
                return true;
            } finally {
                lock.unlock();
            }
        }

        public void stop() {
            stop(saveAtExit);
        }

        public void stop(boolean save) {
            lock.lock();
            try {
                if (opened) {
                    xdb.Trace.info("xdb.util.Dbx stop begin");
                    // remove in loop. 这里并不需要在锁里面关闭数据库。
                    if (save) {
                        for (Dbx dbx : Manager.this.getDbxs()) {
                            dbx.close();
                        }
                    } else {
                        for (Dbx dbx : Manager.this.getDbxs()) {
                            dbx.destroy();
                        }
                    }
                    assert (Manager.this.getDbxs().length == 0);
                    opened = false;
                    xdb.Trace.info("xdb.util.Dbx stop end");
                }
            } finally {
                lock.unlock();
            }
        }

        /**
         * open a DB ( NullLogger only )
         *
         * @param dbConf
         * @param metaData
         * @return
         */
        public Dbx open(MySQLConf dbConf, DatabaseMetaData metaData) {
            lock.lock();
            try {
                Dbx dbx = dbxs.get(dbConf.getDbName());
                if (null == dbx) {
                    dbx = new Dbx(dbConf, metaData);
                    dbxs.put(dbConf.getDbName(), dbx);
                }
                return dbx;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("新建dbx管理出错！", e);
            } finally {
                lock.unlock();
            }
        }

        public Dbx[] getDbxs() {
            lock.lock();
            try {
                return dbxs.values().toArray(new Dbx[dbxs.size()]);
            } finally {
                lock.unlock();
            }

        }

        // called when Dbx close and destroy
        private void removeDbx(Dbx dbx) {
            lock.lock();
            try {
                dbx.mySqlMgr.Exit();
            } finally {
                lock.unlock();
            }
        }

    }

    /**
     * 继承自 xdb.Storage。
     *
     * @see xdb.Storage
     */
    public class Table {
        private final String name;
        private final DatabaseMetaData.Table metaData; // fast reference

        Table(String tableName) {
            this.name = tableName;
            this.metaData = Dbx.this.getMetaData().getTable(tableName);
        }

        /**
         * 表所属的数据库
         *
         * @return
         */
        public Dbx getDatabase() {
            return Dbx.this;
        }

        public DatabaseMetaData.Table getMetaData() {
            return metaData;
        }

        /**
         * @return 表名。
         */
        public String getName() {
            return name;
        }


        public ArrayList<String> getKeys() throws SQLException {
            return mySqlMgr.QueryKeys(name);
        }

        public ArrayList<String> getKeys(Connection conn) throws SQLException {
            return mySqlMgr.QueryKeys(name, conn);
        }

        public boolean insert(OctetsStream key, OctetsStream value) throws MarshalException {
            String insertSql = "INSERT INTO " + getName() + " VALUES('" + key.unmarshalJson() + "', ?)";
            return mySqlMgr.ExecUpdate(insertSql, value) > 0;
        }

        public boolean update(OctetsStream key, OctetsStream value) throws MarshalException {
            String updateSql = "UPDATE " + getName() + " set v = ? where k = '" + key.unmarshalJson() + "'";
            return mySqlMgr.ExecUpdate(updateSql, value) >= 0;
        }

        public boolean update(String key, OctetsStream value) throws MarshalException {
            String updateSql = "UPDATE " + getName() + " set v = ? where k = '" + key + "'";
            return mySqlMgr.ExecUpdate(updateSql, value) >= 0;
        }

        public boolean remove(OctetsStream key) throws MarshalException {
            return remove(key.unmarshalJson());
        }

        public boolean remove(String key) throws MarshalException {
            String deleteSql = "DELETE FROM " + getName() + " where k = '" + key + "'";
            return mySqlMgr.ExecUpdate(deleteSql, null) > 0;
        }

        public OctetsStream _find(OctetsStream key) throws SQLException, MarshalException {
            return _find(key.unmarshalJson());
        }

        public OctetsStream _find(String key) throws SQLException, MarshalException {
            return mySqlMgr.ExecQuery("SELECT V FROM " + getName() + " where k = '" + key + "';");
        }

        public OctetsStream _find(OctetsStream key, Connection conn) throws SQLException, MarshalException {
            return _find(key.unmarshalJson(), conn);
        }

        public OctetsStream _find(String key, Connection conn) throws SQLException, MarshalException {
            return mySqlMgr.ExecQuery("SELECT V FROM " + getName() + " where k = '" + key + "';", conn);
        }


        public boolean _exist(OctetsStream key) throws SQLException, MarshalException {
            return _exist(key.unmarshalJson());
        }

        public boolean _exist(String key) throws SQLException, MarshalException {
            return mySqlMgr.ExecQuery("SELECT V FROM " + getName() + " where k = '" + key + "';") != null;
        }
    }
}
